﻿/*======================================================================
== Copyright : BlueCurve (c)
== Licence   : Gnu/GPL v2.x
== Author    : Teddy Albina
== Email     : bluecurveteam@gmail.com
== Web site  : http://www.codeplex.com/BlueCurve
========================================================================*/
using System;
using System.AddIn.Hosting;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Threading;
using System.Xml.Linq;

namespace BlueCurve.AddInsLoad
{
    /// <summary>
    /// Bibliothéque de chargement des AddIns
    /// http://badger.developpez.com/tutoriels/dotnet/gerer-addin-framework-3-5/
    /// </summary>
    /// <typeparam name="T">Type d'Addin à charger</typeparam>
    public class PluginsLoader<T> : IDisposable
    {
        #region 'Configuration'

        /// <summary>
        /// Chemin où se trouve les Addins
        /// </summary>
        private string m_pluginsLocation = "AddIns";
        /// <summary>
        /// Chemins vers les répertoires d'installations des plugins
        /// </summary>
        public readonly string[] m_pluginsDeclarationFile = new string[]
        {
            @"\XMLdeclaration\AddInFile",
            @"\XMLdeclaration\AddInProtocol"
        };
        /// <summary>
        /// Dictionnaire d'Addins de type fichier et protocol
        /// </summary>
        private Dictionary<string, List<string>> m_PluginsArray = new Dictionary<string, List<string>>();
        /// <summary>
        /// Liste de tout les token d'Addins
        /// </summary>
        private Collection<AddInToken> m_addins = new Collection<AddInToken>();
        /// <summary>
        /// Objet pour la synchronisation
        /// </summary>
        private ReaderWriterLockSlim Threadslock = new ReaderWriterLockSlim();
        /// <summary>
        /// Observe le system de fichier
        /// </summary>
        private FileSystemWatcher Watcher { get; set; }

        /// <summary>
        /// Constructeur
        /// </summary>
        public PluginsLoader()
        {
            Watcher = new FileSystemWatcher(this.m_pluginsLocation) { IncludeSubdirectories = true };
            Bindevents();
        }

        /// <summary>
        /// Constructeur
        /// </summary>
        /// <param name="Addinslocation">Chemin d'accès des AddIns</param>
        public PluginsLoader(string Addinslocation)
        {
            this.m_pluginsLocation = Addinslocation;
            Watcher = new FileSystemWatcher(this.m_pluginsLocation) { IncludeSubdirectories = true };
            Bindevents();
        }

        private void Bindevents()
        {
            Watcher.Changed += new FileSystemEventHandler(Watcher_Changed);
        }

        private void Watcher_Changed(object sender, FileSystemEventArgs e)
        {
            UpdateAddins();
        }

                 
        #endregion






        #region 'Plugins'

        /// <summary>
        /// Obtiens un AddInToken pour le document ou le protocol spécifié
        /// </summary>
        /// <param name="data">Donnée pour la recherche d'Addin</param>
        public AddInToken GetAddInToken(string data)
        {
            Threadslock.EnterReadLock();
            try
            {
                if (string.IsNullOrEmpty(data))
                    return null;

                var token = from x in m_PluginsArray
                            from v in m_addins
                            where x.Value.Contains(data) &&
                            v.Name.Equals(x.Key, StringComparison.InvariantCultureIgnoreCase)
                            select v;

                foreach (var tken in token)
                {
                    return tken;
                }
            }
            finally
            {
                Threadslock.ExitReadLock();
            }
            return null;
        }


        /// <summary>
        /// Obtiens le token d'un AddIn en utilisant son nom
        /// </summary>
        /// <param name="name">Nom de l'AddIn</param>
        /// <returns>AddInToken</returns>
        public AddInToken GetAddInTokenByName(string name)
        {
            Threadslock.EnterReadLock();
            try
            {
                var AddIn = from x in m_addins
                            where x.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)
                            select x;

                foreach (var x in AddIn)
                    return x;
            }
            finally
            {
                Threadslock.ExitReadLock();
            }
            return null;
        }


        /// <summary>
        /// Mets a jour le cache d'AddIns
        /// </summary>
        public void UpdateAddins()
        {
            Threadslock.EnterUpgradeableReadLock();
            try
            {
                Threadslock.EnterWriteLock();
                try
                {
                    m_addins.Clear();
                    m_PluginsArray.Clear();
                }
                finally
                {
                    Threadslock.ExitWriteLock();
                }

                // Mise à jour des addins et du cache du pipeline
                AddInStore.Update(m_pluginsLocation);

                // Découverte et ajout des Addins dans la collection
                m_addins = AddInStore.FindAddIns(typeof(T), m_pluginsLocation);

                List<string> temp = this.GetFiles();
                temp.ForEach(
                    delegate(string path)
                    {
                        if (path.EndsWith(".xml", StringComparison.InvariantCultureIgnoreCase))
                        {
                            XDocument xmldoc = XDocument.Load(path);

                            var dll_name = from x in xmldoc.Elements()
                                           select new
                                           {
                                               Name = x.Element("dlls").Value
                                           };

                            var dll_available = from x in xmldoc.Descendants("available")
                                                select new
                                                {
                                                    Extensions = (from e in x.Elements("match")
                                                                  select e.Value).ToList()
                                                };
                            // Nom de l'AddIn
                            string Name = dll_name.Single().Name;

                            // Lite des extensions ou protocol pris en charde pas le plugin
                            List<string> _available = new List<string>();
                            List<string> Extensions = dll_available.Single().Extensions;
                            Extensions.ForEach(
                                delegate(string ext)
                                {
                                    if (!_available.Contains(ext.ToLower(System.Globalization.CultureInfo.InvariantCulture)))
                                        _available.Add(ext.ToLower(System.Globalization.CultureInfo.InvariantCulture));
                                });

                            Threadslock.EnterWriteLock();
                            try
                            {
                                // Ajout des informations de L'AddIn
                                m_PluginsArray.Add(Name, _available);
                            }
                            finally
                            {
                                Threadslock.ExitWriteLock();
                            }

                            Extensions.Clear();
                        }
                    });
            }
            finally
            {
                Threadslock.ExitUpgradeableReadLock();
            }
        }


        /// <summary>
        /// Obtiens tous les fichiers XML de déclaration
        /// </summary>
        /// <returns><![CDATA[List<string>]]></returns>
        private List<string> GetFiles()
        {
            List<string> paths = new List<string>();
            string _PluginsLocation = m_pluginsLocation.Split('\\')[0];

            for (int a = 0; a < m_pluginsDeclarationFile.Length; a++)
            {
                if (Directory.Exists(string.Concat(_PluginsLocation, m_pluginsDeclarationFile[a])))
                {
                    string[] files = Directory.GetFiles(string.Concat(_PluginsLocation, m_pluginsDeclarationFile[a]));
                    for (int i = 0; i < files.Length; i++)
                    {
                        if (!paths.Contains(Path.GetFullPath(files[i])))
                            paths.Add(Path.GetFullPath(files[i]));
                    }
                }
            }
            return paths;
        }

        #endregion




        #region IDisposable Membres

        /// <summary>
        /// Liberation des ressources
        /// </summary>
        public void Dispose()
        {
            Watcher.Changed -= new FileSystemEventHandler(Watcher_Changed);  
        }

        #endregion
    }
}
